/***************************************************
 * Mod from dots_tb's LOLIcon and Electry's PSVshell
 *    LOLIcon  https://github.com/dots-tb/LOLIcon
 *    PSVshell https://github.com/Electry/PSVshell
 ****************************************************/
#include <vitasdkkern.h>
#include <taihen.h>

#include "main.h"
#include "scr_print.h"
#include "gui.h"
#include "config.h"
#include "utils.h"
#include "menu.h"

enum AppType
{
	APP_HOME,
	APP_SYSTEM,
	APP_GAME,
	APP_BLACKLISTED,
};

typedef struct SceKernelSystemInfo
{
	SceSize size;
	SceUInt32 activeCpuMask;
	struct
	{
		SceKernelSysClock idleClock;
		SceUInt32 comesOutOfIdleCount;
		SceUInt32 threadSwitchCount;
	} cpuInfo[4];
} SceKernelSystemInfo;

typedef struct SceSysmemAddressSpaceInfo
{
	uintptr_t base;
	uint32_t total;
	uint32_t free;
	uint32_t unkC;
} SceSysmemAddressSpaceInfo;

#define THE_FLOW_MAGIC 0x6183015

#define REFRESH_CPU_DELAY_MICROS (500 * 1000)

#define MAX_HOOKS_NUM 18
static tai_hook_ref_t s_hook_refs[MAX_HOOKS_NUM];
static SceUID s_hooks[MAX_HOOKS_NUM];
static SceUID s_injects[1];

uint32_t *ScePower_41C8 = NULL;
uint32_t *ScePower_41CC = NULL;
uint32_t *ScePower_0 = NULL;

#define HOME_TITLEID "HOME"
#define MAX_TITLEID_LENGTH 16
char g_titleid[MAX_TITLEID_LENGTH] = HOME_TITLEID;
int g_app_type = APP_HOME;
int g_fps = 0;
BatteryInfo g_battery_info = {0};
MemoryInfo g_memory_info = {0};
int g_cpu_usages[MAX_CPU_NUM] = {0};

#define INVALID_PID -1
SceUID s_pid = INVALID_PID;

static SceUID s_framebuf_mutex_uid = -1;
static SceUID s_cpufreq_mutex_uid = -1;
static SceUID s_procevent_mutex_uid = -1;

static SceUID s_thread_uid = -1;
static uint8_t s_thread_run = 1;

static uint8_t s_is_interfere_setting = 0;

extern int module_get_export_func(SceUID pid, const char *modname, uint32_t libnid, uint32_t funcnid, uintptr_t *func);
extern int module_get_offset(SceUID pid, SceUID modid, int segidx, size_t offset, uintptr_t *addr);
extern SceUInt64 ksceKernelGetProcessTimeWideCore();
extern SceUInt32 ksceKernelSysrootGetCurrentAddressSpaceCB();
extern SceBool ksceAppMgrIsExclusiveProcessRunning();

int (*_SceSysmemForKernel_0x3650963F)(uint32_t a1, SceSysmemAddressSpaceInfo *a2);
int (*_SceThreadmgrForDriver_0x7E280B69)(SceKernelSystemInfo *pInfo);
int (*_ScePervasiveForDriver_0xE9D95643)(int mul, int ndiv);

int (*_kscePowerGetArmClockFrequency)(void);
int (*_kscePowerGetBusClockFrequency)(void);
int (*_kscePowerGetGpuClockFrequencyInternal)(int *corefreq, int *mpfreq);
int (*_kscePowerGetGpuXbarClockFrequency)(void);

int (*_kscePowerSetArmClockFrequency)(int freq);
int (*_kscePowerSetBusClockFrequency)(int freq);
int (*_kscePowerSetGpuClockFrequencyInternal)(int corefreq, int mpfreq);
int (*_kscePowerSetGpuXbarClockFrequency)(int freq);

int exitGame()
{
	if (g_app_type != APP_HOME && g_app_type != APP_BLACKLISTED && s_pid != INVALID_PID && s_pid != KERNEL_PID)
		return ksceAppMgrKillProcess(s_pid);
	return 0;
}

void refreshClockFreqs()
{
	s_is_interfere_setting = 1;
	if (g_cpu_clock_values[g_setting_config.cpu_clock] > 0)
		_kscePowerSetArmClockFrequency(g_cpu_clock_values[g_setting_config.cpu_clock]);
	if (g_bus_clock_values[g_setting_config.bus_clock] > 0)
		_kscePowerSetBusClockFrequency(g_bus_clock_values[g_setting_config.bus_clock]);
	if (g_gpu_clock_values[g_setting_config.gpu_clock] > 0)
		_kscePowerSetGpuClockFrequencyInternal(g_gpu_clock_values[g_setting_config.gpu_clock], g_gpu_clock_values[g_setting_config.gpu_clock]);
	if (g_xbr_clock_values[g_setting_config.xbr_clock] > 0)
		_kscePowerSetGpuXbarClockFrequency(g_xbr_clock_values[g_setting_config.xbr_clock]);
	s_is_interfere_setting = 0;
}

static void refreshFps()
{
	static SceUInt64 last_micros = 0;
	static int frames = 0;

	SceUInt64 cur_micros = ksceKernelGetProcessTimeWideCore();
	SceUInt64 interval_micros = cur_micros - last_micros;
	if (interval_micros >= 1000000)
	{
		g_fps = (frames / (double)interval_micros) * 1000000.0f + 0.5f;
		last_micros = cur_micros;
		frames = 0;
	}
	frames++;
}

static void refreshBattery()
{
	if (!g_battery_info.is_dolce)
	{
		g_battery_info.is_low_battery = kscePowerIsLowBattery();
		g_battery_info.is_battery_charging = kscePowerIsBatteryCharging();
		int battery_percent = kscePowerGetBatteryLifePercent();
		if (battery_percent >= 0)
			g_battery_info.battery_percent = battery_percent;
	}
}

static void refreshCpu()
{
	static SceUInt64 last_micros = 0;
	static SceKernelSysClock last_cpu_idle_clocks[4] = {0};

	SceUInt64 cur_micros = ksceKernelGetProcessTimeWideCore();
	SceUInt64 interval_micros = cur_micros - last_micros;
	// Calculate AVG CPU usage
	if (interval_micros >= REFRESH_CPU_DELAY_MICROS)
	{
		SceKernelSystemInfo info;
		info.size = sizeof(SceKernelSystemInfo);
		_SceThreadmgrForDriver_0x7E280B69(&info);

		for (int i = 0; i < MAX_CPU_NUM; i++)
		{
			g_cpu_usages[i] = (int)(100.0f - ((info.cpuInfo[i].idleClock - last_cpu_idle_clocks[i]) / (float)interval_micros) * 100);
			if (g_cpu_usages[i] < 0)
				g_cpu_usages[i] = 0;
			if (g_cpu_usages[i] > 100)
				g_cpu_usages[i] = 100;
			last_cpu_idle_clocks[i] = info.cpuInfo[i].idleClock;
		}

		last_micros = cur_micros;
	}
}

static void refreshMemory()
{
	// Takes caller's context into account
	SceSysmemAddressSpaceInfo info;
	uint32_t sysroot_cas = ksceKernelSysrootGetCurrentAddressSpaceCB();

	if (*(uint32_t *)(sysroot_cas + 328) > 0)
	{
		_SceSysmemForKernel_0x3650963F(*(uint32_t *)(sysroot_cas + 328), &info);
		g_memory_info.main_usage = info.total - info.free;
		g_memory_info.main_total = info.total;
	}
	else
	{
		g_memory_info.main_usage = 0;
		g_memory_info.main_total = 0;
	}

	if (*(uint32_t *)(sysroot_cas + 332) > 0)
	{
		_SceSysmemForKernel_0x3650963F(*(uint32_t *)(sysroot_cas + 332), &info);
		g_memory_info.cdram_usage = info.total - info.free;
		g_memory_info.cdram_total = info.total;
	}
	else
	{
		g_memory_info.cdram_usage = 0;
		g_memory_info.cdram_total = 0;
	}

	if (*(uint32_t *)(sysroot_cas + 316) > 0)
	{
		_SceSysmemForKernel_0x3650963F(*(uint32_t *)(sysroot_cas + 316), &info);
		g_memory_info.phycont_usage = info.total - info.free;
		g_memory_info.phycont_total = info.total;
	}
	else
	{
		g_memory_info.phycont_usage = 0;
		g_memory_info.phycont_total = 0;
	}
}

static int mainThreadEntry(SceSize args, void *argp)
{
	while (s_thread_run)
	{
		// Don't do anything if is in blacklist
		if (g_app_type == APP_BLACKLISTED)
		{
			ksceKernelDelayThread(200 * 1000);
			continue;
		}

		// Check buttons
		SceCtrlData ctrl;
		memset(&ctrl, 0, sizeof(SceCtrlData));
		int ret = ksceCtrlPeekBufferPositive(0, &ctrl, 1);
		if (ret < 0)
			ret = ksceCtrlPeekBufferPositive(1, &ctrl, 1);
		if (ret >= 0)
		{
			if (!isShowingMenu())
			{
				readPad(&ctrl, 0);
				if (isKeyCurrent(SCE_CTRL_UP) && isKeyCurrent(SCE_CTRL_SELECT))
					openMenu();
			}
		}

		if (g_setting_config.show_battery)
			refreshBattery();
		if (g_setting_config.show_cpu)
			refreshCpu();

		ksceKernelDelayThread(50 * 1000);
	}

	return 0;
}

int startMainThread()
{
	s_thread_uid = ksceKernelCreateThread("kernel_main_thread", mainThreadEntry, 0x3C, 0x3000, 0, 0x10000, 0);
	if (s_thread_uid < 0)
		return s_thread_uid;
	return ksceKernelStartThread(s_thread_uid, 0, NULL);
}

void finishMainThread()
{
	if (s_thread_uid >= 0)
	{
		s_thread_run = 0;
		ksceKernelWaitThreadEnd(s_thread_uid, NULL, NULL);
		ksceKernelDeleteThread(s_thread_uid);
		s_thread_uid = -1;
	}
}

static int ksceDisplaySetFrameBufInternal_patched(int head, int index, const SceDisplayFrameBuf *pParam, int sync)
{
	static int hook_drawing = 0;

	if (hook_drawing || sync != SCE_DISPLAY_SETBUF_NEXTFRAME)
		goto DISPLAY_HOOK_RET;

	if (g_app_type == APP_BLACKLISTED)
		goto DISPLAY_HOOK_RET;

	if (head != ksceDisplayGetPrimaryHead() || !pParam || !pParam->base)
		goto DISPLAY_HOOK_RET;

	if (index && (ksceAppMgrIsExclusiveProcessRunning() || g_app_type == APP_GAME)) // Do not draw over SceShell overlay
		goto DISPLAY_HOOK_RET;

	if (ksceKernelLockMutex(s_framebuf_mutex_uid, 1, NULL) < 0)
		goto DISPLAY_HOOK_RET;

	if (guiIsShowing())
	{
		if (g_setting_config.show_fps)
			refreshFps();
		if (g_setting_config.show_memory)
			refreshMemory();

		scrPrintSetFrameBuf(pParam);
		guiDrawAll();
		guiCtrlAll();

		if (isShowingMenu() && g_setting_config.fix_flicker && g_app_type == APP_GAME)
		{
			// update now to fix flicker when vblank period is missed
			ksceKernelUnlockMutex(s_framebuf_mutex_uid, 1);

			hook_drawing = 1;
			int ret = TAI_CONTINUE(int, s_hook_refs[0], head, index, pParam, SCE_DISPLAY_SETBUF_IMMEDIATE);
			ret = ksceDisplaySetFrameBufInternal(head, index, pParam, SCE_DISPLAY_SETBUF_NEXTFRAME);
			hook_drawing = 0;
			return ret;
		}
	}

	ksceKernelUnlockMutex(s_framebuf_mutex_uid, 1);

DISPLAY_HOOK_RET:
	return TAI_CONTINUE(int, s_hook_refs[0], head, index, pParam, sync);
}

static int ctrlPatched(tai_hook_ref_t hook_ref, int port, SceCtrlData *ctrl, int count, int negative)
{
	if (hook_ref == 0)
		return 1;

	int ret = TAI_CONTINUE(int, hook_ref, port, ctrl, count);
	if (ret < 0 || (port != 0 && port != 1))
		return ret;

	if (isShowingMenu())
	{
		uint32_t buttons = negative ? 0xFFFFFFFF : 0;
		ksceKernelMemcpyKernelToUser(&ctrl->buttons, &buttons, sizeof(uint32_t));
	}

	return ret;
}

static int sceCtrlPeekBufferPositive_patched(int port, SceCtrlData *ctrl, int count)
{
	return ctrlPatched(s_hook_refs[1], port, ctrl, count, 0);
}

static int sceCtrlPeekBufferPositive2_patched(int port, SceCtrlData *ctrl, int count)
{
	return ctrlPatched(s_hook_refs[2], port, ctrl, count, 0);
}

static int sceCtrlReadBufferPositive_patched(int port, SceCtrlData *ctrl, int count)
{
	return ctrlPatched(s_hook_refs[3], port, ctrl, count, 0);
}

static int sceCtrlReadBufferPositive2_patched(int port, SceCtrlData *ctrl, int count)
{
	return ctrlPatched(s_hook_refs[4], port, ctrl, count, 0);
}

static int sceCtrlPeekBufferPositiveExt_patched(int port, SceCtrlData *ctrl, int count)
{
	return ctrlPatched(s_hook_refs[5], port, ctrl, count, 0);
}

static int sceCtrlPeekBufferPositiveExt2_patched(int port, SceCtrlData *ctrl, int count)
{
	return ctrlPatched(s_hook_refs[6], port, ctrl, count, 0);
}

static int sceCtrlReadBufferPositiveExt_patched(int port, SceCtrlData *ctrl, int count)
{
	return ctrlPatched(s_hook_refs[7], port, ctrl, count, 0);
}

static int sceCtrlReadBufferPositiveExt2_patched(int port, SceCtrlData *ctrl, int count)
{
	return ctrlPatched(s_hook_refs[8], port, ctrl, count, 0);
}

static int sceCtrlPeekBufferNegative_patched(int port, SceCtrlData *ctrl, int count)
{
	return ctrlPatched(s_hook_refs[9], port, ctrl, count, 1);
}

static int sceCtrlPeekBufferNegative2_patched(int port, SceCtrlData *ctrl, int count)
{
	return ctrlPatched(s_hook_refs[10], port, ctrl, count, 1);
}

static int sceCtrlReadBufferNegative_patched(int port, SceCtrlData *ctrl, int count)
{
	return ctrlPatched(s_hook_refs[11], port, ctrl, count, 1);
}

static int sceCtrlReadBufferNegative2_patched(int port, SceCtrlData *ctrl, int count)
{
	return ctrlPatched(s_hook_refs[12], port, ctrl, count, 1);
}

static int kscePowerSetArmClockFrequency_patched(int freq)
{
	int ret = ksceKernelLockMutex(s_cpufreq_mutex_uid, 1, NULL);
	if (ret < 0)
		goto HOOK_RET;

	if (!s_is_interfere_setting)
		g_cpu_clock_values[0] = freq;
	if (g_setting_config.cpu_clock > 0)
		freq = g_cpu_clock_values[g_setting_config.cpu_clock];

	if (freq > 444 && freq <= 500)
	{
		ret = TAI_CONTINUE(int, s_hook_refs[13], 444);
		if (ScePower_41C8 && ScePower_41CC)
		{
			// Apply mul:div (15:0)
			_ScePervasiveForDriver_0xE9D95643(15, 16 - 0);
			// Store global freq & mul for kscePowerGetArmClockFrequency()
			*ScePower_41C8 = 500;
			*ScePower_41CC = 15;
		}
		ksceKernelUnlockMutex(s_cpufreq_mutex_uid, 1);
		return ret;
	}

	ksceKernelUnlockMutex(s_cpufreq_mutex_uid, 1);

HOOK_RET:
	return TAI_CONTINUE(int, s_hook_refs[13], freq);

	return ret;
}

static int kscePowerSetBusClockFrequency_patched(int freq)
{
	if (!s_is_interfere_setting)
		g_bus_clock_values[0] = freq;
	if (g_setting_config.bus_clock > 0)
		freq = g_bus_clock_values[g_setting_config.bus_clock];

	return TAI_CONTINUE(int, s_hook_refs[14], freq);
}

static int kscePowerSetGpuClockFrequencyInternal_patched(int corefreq, int mpfreq)
{
	if (!s_is_interfere_setting)
		g_gpu_clock_values[0] = corefreq;
	if (g_setting_config.gpu_clock > 0)
		corefreq = mpfreq = g_gpu_clock_values[g_setting_config.gpu_clock];

	return TAI_CONTINUE(int, s_hook_refs[15], corefreq, mpfreq);
}

static int kscePowerSetGpuXbarClockFrequency_patched(int freq)
{
	if (!s_is_interfere_setting)
		g_xbr_clock_values[0] = freq;
	if (g_setting_config.xbr_clock > 0)
		freq = g_xbr_clock_values[g_setting_config.xbr_clock];

	return TAI_CONTINUE(int, s_hook_refs[16], freq);
}

int ksceKernelInvokeProcEventHandler_patched(int pid, int ev, int a3, int a4, int *a5, int a6)
{
	int ret = ksceKernelLockMutex(s_procevent_mutex_uid, 1, NULL);
	if (ret < 0)
		goto PROCEVENT_EXIT;

	char titleid[MAX_TITLEID_LENGTH];
	strncpy(titleid, g_titleid, MAX_TITLEID_LENGTH);

	switch (ev)
	{
	case 1: // startup
	case 5: // resume
		if (g_app_type != APP_HOME)
			goto PROCEVENT_UNLOCK_EXIT;

		ksceKernelGetProcessTitleId(pid, titleid, sizeof(titleid));
		// Check app type
		if (ksceSblACMgrIsPspEmu(pid)) // if pid is PspEmu
			g_app_type = APP_BLACKLISTED;
		else if (strncmp(titleid, "NPXS10079", 9) == 0) // Ignore "daily checker bg"
			goto PROCEVENT_UNLOCK_EXIT;
		else if (strncmp(titleid, "NPXS", 4) == 0)
			g_app_type = APP_SYSTEM;
		else
			g_app_type = APP_GAME;
		break;

	case 3: // exit
	case 4: // suspend
		g_app_type = APP_HOME;
		strncpy(titleid, HOME_TITLEID, MAX_TITLEID_LENGTH);
		break;
	}

	if (ev == 1 || ev == 5 || ev == 3 || ev == 4)
	{ // Check titleid changed
		if (strcmp(g_titleid, titleid) != 0)
		{
			saveConfig();
			// Set current titleid
			strncpy(g_titleid, titleid, MAX_TITLEID_LENGTH);
			// Set current pid
			s_pid = (ev == 1 || ev == 5) ? pid : INVALID_PID;

			if (g_app_type == APP_BLACKLISTED)
			{ // Don't do anything if is in blacklist
				resetConfig();
				if (guiIsShowing())
					guiCloseAll();
			}
			else
			{
				loadConfig();
			}
		}
	}

PROCEVENT_UNLOCK_EXIT:
	ksceKernelUnlockMutex(s_procevent_mutex_uid, 1);

PROCEVENT_EXIT:
	return TAI_CONTINUE(int, s_hook_refs[17], pid, ev, a3, a4, a5, a6);
}

void _start() __attribute__((weak, alias("module_start")));
int module_start(SceSize argc, const void *args)
{
	if (ksceSblAimgrIsGenuineDolce())
		g_battery_info.is_dolce = 1;

	tai_module_info_t tai_info;
	tai_info.size = sizeof(tai_module_info_t);
	taiGetModuleInfoForKernel(KERNEL_PID, "ScePower", &tai_info);

	module_get_offset(KERNEL_PID, tai_info.modid, 1, 0x41C8, (uintptr_t *)&ScePower_41C8);
	module_get_offset(KERNEL_PID, tai_info.modid, 1, 0x41CC, (uintptr_t *)&ScePower_41CC);
	module_get_offset(KERNEL_PID, tai_info.modid, 1, 0x0, (uintptr_t *)&ScePower_0);

	module_get_export_func(KERNEL_PID, "ScePower", 0x1590166F, 0xABC6F88F, (uintptr_t *)&_kscePowerGetArmClockFrequency);
	module_get_export_func(KERNEL_PID, "ScePower", 0x1590166F, 0x478FE6F5, (uintptr_t *)&_kscePowerGetBusClockFrequency);
	module_get_export_func(KERNEL_PID, "ScePower", 0x1590166F, 0x475BCC82, (uintptr_t *)&_kscePowerGetGpuClockFrequencyInternal);
	module_get_export_func(KERNEL_PID, "ScePower", 0x1590166F, 0x0A750DEE, (uintptr_t *)&_kscePowerGetGpuXbarClockFrequency);

	module_get_export_func(KERNEL_PID, "ScePower", 0x1590166F, 0x74DB5AE5, (uintptr_t *)&_kscePowerSetArmClockFrequency);
	module_get_export_func(KERNEL_PID, "ScePower", 0x1590166F, 0xB8D7B3FB, (uintptr_t *)&_kscePowerSetBusClockFrequency);
	module_get_export_func(KERNEL_PID, "ScePower", 0x1590166F, 0x264C24FC, (uintptr_t *)&_kscePowerSetGpuClockFrequencyInternal);
	module_get_export_func(KERNEL_PID, "ScePower", 0x1590166F, 0xA7739DBE, (uintptr_t *)&_kscePowerSetGpuXbarClockFrequency);

	if (module_get_export_func(KERNEL_PID, "SceSysmem", 0x63A519E5, 0x3650963F, (uintptr_t *)&_SceSysmemForKernel_0x3650963F) < 0) // 3.60
		module_get_export_func(KERNEL_PID, "SceSysmem", 0x02451F0F, 0xB9B69700, (uintptr_t *)&_SceSysmemForKernel_0x3650963F);	   // 3.63
	module_get_export_func(KERNEL_PID, "SceKernelThreadMgr", 0xE2C40624, 0x7E280B69, (uintptr_t *)&_SceThreadmgrForDriver_0x7E280B69);
	module_get_export_func(KERNEL_PID, "SceLowio", 0xE692C727, 0xE9D95643, (uintptr_t *)&_ScePervasiveForDriver_0xE9D95643);

	const uint8_t nop[] = {0x00, 0xBF};
	s_injects[0] = taiInjectAbsForKernel(KERNEL_PID, (void *)((uintptr_t)_ScePervasiveForDriver_0xE9D95643 + 0x1D), &nop, 2);

	s_framebuf_mutex_uid = ksceKernelCreateMutex("kernel_framebuf_mutex", 0, 0, NULL);
	s_cpufreq_mutex_uid = ksceKernelCreateMutex("kernel_cpufreq_mutex", 0, 0, NULL);
	s_procevent_mutex_uid = ksceKernelCreateMutex("kernel_procevent_mutex", 0, 0, NULL);

	loadConfig();

	s_hooks[0] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[0], "SceDisplay", 0x9FED47AC, 0x16466675, ksceDisplaySetFrameBufInternal_patched);

	s_hooks[1] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[1], "SceCtrl", 0xD197E3C7, 0xA9C3CED6, sceCtrlPeekBufferPositive_patched);
	s_hooks[2] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[2], "SceCtrl", 0xD197E3C7, 0x15F81E8C, sceCtrlPeekBufferPositive2_patched);
	s_hooks[3] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[3], "SceCtrl", 0xD197E3C7, 0x67E7AB83, sceCtrlReadBufferPositive_patched);
	s_hooks[4] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[4], "SceCtrl", 0xD197E3C7, 0xC4226A3E, sceCtrlReadBufferPositive2_patched);
	s_hooks[5] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[5], "SceCtrl", 0xD197E3C7, 0xA59454D3, sceCtrlPeekBufferPositiveExt_patched);
	s_hooks[6] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[6], "SceCtrl", 0xD197E3C7, 0x860BF292, sceCtrlPeekBufferPositiveExt2_patched);
	s_hooks[7] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[7], "SceCtrl", 0xD197E3C7, 0xE2D99296, sceCtrlReadBufferPositiveExt_patched);
	s_hooks[8] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[8], "SceCtrl", 0xD197E3C7, 0xA7178860, sceCtrlReadBufferPositiveExt2_patched);
	s_hooks[9] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[9], "SceCtrl", 0xD197E3C7, 0x104ED1A7, sceCtrlPeekBufferNegative_patched);
	s_hooks[10] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[10], "SceCtrl", 0xD197E3C7, 0x81A89660, sceCtrlPeekBufferNegative2_patched);
	s_hooks[11] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[11], "SceCtrl", 0xD197E3C7, 0x15F96FB0, sceCtrlReadBufferNegative_patched);
	s_hooks[12] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[12], "SceCtrl", 0xD197E3C7, 0x27A0C5FB, sceCtrlReadBufferNegative2_patched);

	s_hooks[13] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[13], "ScePower", 0x1590166F, 0x74DB5AE5, kscePowerSetArmClockFrequency_patched);
	s_hooks[14] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[14], "ScePower", 0x1590166F, 0xB8D7B3FB, kscePowerSetBusClockFrequency_patched);
	s_hooks[15] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[15], "ScePower", 0x1590166F, 0x264C24FC, kscePowerSetGpuClockFrequencyInternal_patched);
	s_hooks[16] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[16], "ScePower", 0x1590166F, 0xA7739DBE, kscePowerSetGpuXbarClockFrequency_patched);

	s_hooks[17] = taiHookFunctionImportForKernel(KERNEL_PID, &s_hook_refs[17], "SceProcessmgr", 0x887F19D0, 0x414CC813, ksceKernelInvokeProcEventHandler_patched);

	startMainThread();

	return SCE_KERNEL_START_SUCCESS;
}

int module_stop(SceSize argc, const void *args)
{
	finishMainThread();

	int i;
	for (i = 0; i < MAX_HOOKS_NUM; i++)
	{
		if (s_hooks[i] >= 0)
			taiHookReleaseForKernel(s_hooks[i], s_hook_refs[i]);
	}

	if (s_injects[0] >= 0)
		taiInjectReleaseForKernel(s_injects[0]);

	if (s_framebuf_mutex_uid >= 0)
		ksceKernelDeleteMutex(s_framebuf_mutex_uid);
	if (s_cpufreq_mutex_uid >= 0)
		ksceKernelDeleteMutex(s_cpufreq_mutex_uid);
	if (s_procevent_mutex_uid >= 0)
		ksceKernelDeleteMutex(s_procevent_mutex_uid);

	return SCE_KERNEL_STOP_SUCCESS;
}
